#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "grammar_parser.hpp"
#include "enginefactory.h"

#include <QTextEdit>
#include <QPushButton>
#include <QMessageBox>
#include <QFile>
#include <QTextStream>
#include <QSettings>
#include <QFileDialog>
#include <QMouseEvent>
#include <QDebug>

static const auto AutoWriteToFile = "autoWriteToFile";

static const auto saveStringToFile = [](const QString &fileName, const QString &content)
{
    QFile openFile(fileName);
    if (!openFile.open(QIODevice::WriteOnly))
        return;
    QTextStream stream(&openFile);
    stream << content;
    stream.flush();
    openFile.flush();
    openFile.close();
};

static auto readFileContext(const QString &filePath)
{
    QFile readFile(filePath);
    if (!readFile.open(QIODevice ::ReadOnly))
        return QString();
    QTextStream stream(&readFile);
    return stream.readAll();
};

static auto getStyle(const QString &style, int scaledSize = 96)
{
    qDebug() << "Scaled size is " << scaledSize;
    QFile qss(style);
    if (!qss.open(QFile::ReadOnly))
    {
        qWarning() << QStringLiteral("加载样式文件失败:") << style;
    }
    QString strStyle = qss.readAll();
    qss.close();
    return strStyle;
}

static const std::unordered_map<PathKind, QString> pathKindText = {
    std::make_pair(PathKind::BaseHpp, "BaseHpp"),
    std::make_pair(PathKind::DerivedHpp, "DerivedHpp"),
    std::make_pair(PathKind::DerivedCpp, "DerivedCpp"),
    std::make_pair(PathKind::GrammarPath, "GrammarPath"),
};

std::unique_ptr<QSettings> MainWindow::setting_ = nullptr;
std::unordered_map<QLineEdit *, PathKind> MainWindow::routing_;

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent), ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    connect(ui->btnClear, &QPushButton::clicked, [&]()
            {
        for (auto edit : this->findChildren<QTextEdit*>())
            edit->clear(); });
    connect(ui->btnStartGenerate, &QPushButton::clicked, [=]()
            {
        if(ui->editGrammarContent->toPlainText().isEmpty()){
            const auto grammarContent = readFileContext(getPath(PathKind::GrammarPath));
            ui->editGrammarContent->setText(grammarContent);
        }
        ui->btnOk->click(); });
    connect(ui->btnOk, &QPushButton::clicked, [&]()
            {
        generateBaseVisitor();
        generateVisitor(); });
    setting_.reset(new QSettings("generatorSetting.ini", QSettings::Format::IniFormat));

    ui->editBaseHppPath->setText(this->getPath(PathKind::BaseHpp));
    ui->editDerivedHppPath->setText(this->getPath(PathKind::DerivedHpp));
    ui->editDerivedCppPath->setText(this->getPath(PathKind::DerivedCpp));
    ui->editGrammarPath->setText(this->getPath(PathKind::GrammarPath));

    routing_ = {
        std::make_pair(ui->editBaseHppPath, PathKind::BaseHpp),
        std::make_pair(ui->editDerivedHppPath, PathKind::DerivedHpp),
        std::make_pair(ui->editDerivedCppPath, PathKind::DerivedCpp),
        std::make_pair(ui->editGrammarPath, PathKind::GrammarPath),
    };
    for (const auto lineEdit : this->findChildren<QLineEdit *>())
    {
        lineEdit->installEventFilter(this);
        auto record = routing_.find(lineEdit);
        if (record != routing_.end())
        {
            record->first->setText(this->getPath(record->second));
            connect(lineEdit, &QLineEdit::textChanged, [=](const QString &text)
                    { setPath(record->second, text); });
        }
    }
    autoWriteToFile_ = setting_->value(AutoWriteToFile, false).toBool();
    autoWriteToFile_ ? ui->radioAutoSave->setChecked(true) : ui->radioDonnotSave->setChecked(true);
    foreach (auto radio, ui->autoSaveSetting->findChildren<QRadioButton *>())
    {
        connect(radio, &QRadioButton::clicked, [=]()
                { setAutoSaveToFile(ui->radioAutoSave == radio); });
    }

    setStyleSheet(getStyle(":/qdarkstyle/dark/style.qss"));
    showMaximized();
}

MainWindow::~MainWindow()
{
    delete ui;
}

bool MainWindow::eventFilter(QObject *watched, QEvent *evt)
{
    if (auto lineEdit = dynamic_cast<QLineEdit *>(watched))
        if (routing_.find(lineEdit) != routing_.end() && evt->type() == QEvent::MouseButtonPress && dynamic_cast<QMouseEvent *>(evt)->button() == Qt::MouseButton::LeftButton)
        {
            auto filePath = QFileDialog::getOpenFileName(this);
            lineEdit->setText(filePath);
            this->setPath(routing_.find(lineEdit)->second, filePath);
        }
    return QObject::eventFilter(watched, evt);
};

QString MainWindow::getPath(PathKind kind)
{
    return setting_->value(pathKindText.find(kind)->second).toString();
}

void MainWindow::setPath(PathKind kind, const QString &value)
{
    setting_->setValue(pathKindText.find(kind)->second, value);
}

void MainWindow::generateBaseVisitor()
{
    GrammarParser grammarParser;
    grammarParser.init(ui->editGrammarContent->toPlainText().toStdString());
    if (auto elements = grammarParser.ruleElementCollections())
    {
        BaseVisitor visitor;
        visitor.visibleRuleCollections = elements;
        visitor.invisibleRuleCollections = grammarParser.invisibleRuleCollections();
        EngineFactory factory;
        const auto baseContent = factory.genCode(visitor);
        ui->editAbstractVisitor->setText(baseContent);
        if (autoSaveToFile())
            saveStringToFile(getPath(PathKind::BaseHpp), baseContent);
    }
}

void MainWindow::generateVisitor()
{
    GrammarParser grammarParser;
    grammarParser.init(ui->editGrammarContent->toPlainText().toStdString());
    if (auto elements = grammarParser.ruleElementCollections())
    {
        SpecificTypeVisitor visitor;
        visitor.visibleRuleCollections = elements;
        visitor.invisibleRuleCollections = grammarParser.invisibleRuleCollections();
        visitor.className = "JsonVisitor";
        visitor.returnType = "Json";
        visitor.baseType = "JsonVisitorHelper";
        EngineFactory factory;
        auto headerAndImpl = factory.genCode(visitor);
        ui->editHeader->setText(headerAndImpl.first);
        ui->editImpl->setText(headerAndImpl.second);
        if (autoSaveToFile())
        {
            saveStringToFile(getPath(PathKind::DerivedHpp), headerAndImpl.first);
            saveStringToFile(getPath(PathKind::DerivedCpp), headerAndImpl.second);
        }
    }
}

void MainWindow::setAutoSaveToFile(bool enable)
{
    autoWriteToFile_ = enable;
    setting_->setValue(AutoWriteToFile, enable);
}

bool MainWindow::autoSaveToFile() const
{
    return autoWriteToFile_;
}
